package zorm

import (
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"
)

var m_key map[string]map[string]string

func init() {
	mysqlkey := make(map[string]string)
	mysqlkey["id"] = "SERIAL NOT NULL"

	mysqlkey["int"] = "INT NOT NULL"
	mysqlkey["string"] = "TEXT NOT NULL" //TEXT 没有性能影响
	mysqlkey["bool"] = "BOOL NOT NULL"
	mysqlkey["float"] = "NUMERIC(6,3) NOT NULL" //强制10万  最小3位小数
	mysqlkey["many2many"] = "TEXT NOT NULL"
	mysqlkey["many2one"] = "INT NOT NULL"
	mysqlkey["json"] = "TEXT"
	mysqlkey["datetime"] = "TIMESTAMP"
	mysqlkey["int64"] = "BIGINT"
	mysqlkey["uint8"] = "tinyint"
	mysqlkey["_insert_retid"] = "" //添加数据时，需要返回ID
	mysqlkey["字段区分符号"] = "`"

	postgreskey := make(map[string]string)
	postgreskey["int"] = "integer"
	postgreskey["string"] = "character varying" //TEXT 没有性能影响
	postgreskey["bool"] = "boolean"
	postgreskey["float"] = "double precision" //强制10万  最小3位小数
	postgreskey["many2many"] = "character varying"
	postgreskey["many2one"] = "integer"
	postgreskey["json"] = "character varying"
	postgreskey["datetime"] = "timestamp without time zone"
	postgreskey["int64"] = "BIGINT"
	postgreskey["uint8"] = "integer"

	postgreskey["id"] = "SERIAL primary key"
	postgreskey["_insert_retid"] = "RETURNING id"

	postgreskey["字段区分符号"] = ""
	//添加到总表里
	m_key = make(map[string]map[string]string)
	m_key["mysql"] = mysqlkey
	m_key["postgres"] = postgreskey

}

//功能 创建一个表
func (z *ZDB) CreateTable(tabptr interface{}) error {
	var sqls string
	if z.isDebug == true {
		st := time.Now()
		defer func() {
			fmt.Printf("ORM\t%s\t%f\t%s\r\n", time.Now().Format("15:04:05"), time.Now().Sub(st).Seconds(), sqls)
		}()
	}
	fs, err := GetTableInfo(tabptr)
	if err != nil {
		return err
	}
	if strings.Split(fs.GoName, ".")[0] == "*zorm" { //如果是系统表，则不创建
		return nil
	}
	index := make(map[string]string) //创建索引
	dbkey := m_key[z.dbtype]
	区分符 := dbkey["字段区分符号"]
	for i, k := range fs.Cols {
		if i != 0 { //字段间的分隔符
			sqls += ","
		}
		if v, ok := fs.Fields[k]; ok == false { //获取字段属性
			panic(err)
		} else {
			sqls += 区分符 + v.Field + 区分符 + " "
			if strings.ToLower(k) == "id" {
				sqls += dbkey["id"]
				continue
			}
			switch v.Type {
			case TYPE_INT, TYPE_UINT16, TYPE_INT16, TYPE_INT32:
				sqls += dbkey["int"]
			case TYPE_STRING:
				sqls += dbkey["string"]
			case TYPE_BOOL:
				sqls += dbkey["bool"]
			case TYPE_FLOAT64:
				sqls += dbkey["float"]
			case TYPE_MANY2MANY:
				sqls += dbkey["many2many"]
			case TYPE_MANY2ONE:
				sqls += dbkey["many2one"]
			case TYPE_JSON:
				sqls += dbkey["json"]
			case TYPE_DATETIME: //日期类型，并设置默认值
				sqls += dbkey["datetime"] //DEFAULT CURRENT_TIMESTAMP
			case TYPE_INT64:
				sqls += dbkey["int64"]
			case TYPE_UINT8:
				sqls += dbkey["uint8"]

			default:
				panic("未知类型" + strconv.Itoa(v.Type))
			}
			if len(v.IndexName) != 0 {
				ind, ok := index[v.IndexName]
				if ok == false {
					index[v.IndexName] = v.Field
				} else {
					ind += "," + v.Field
					index[v.IndexName] = v.Field
				}
			}
		}
	}
	sqls = "CREATE TABLE  " + GetTableName(tabptr) + "(" + sqls

	if z.dbtype == "mysql" {
		//添加主键
		sqls += ",primary key (id)"
		//添加索引
		for k, v := range index {
			sqls += ",UNIQUE INDEX `" + k + "` (`" + v + "`) USING BTREE "
		}
	}
	sqls += ")"
	if _, err := z.Db.Exec(sqls); err != nil {
		return err
	}
	return nil
}

//是否存在表
func (z *ZDB) HasTable(strptr interface{}) bool {
	tabname := GetTableName(strptr)
	switch z.dbtype {
	case "postgres":
		sqls := "SELECT to_regclass('" + tabname + "') is not null"
		if z.isDebug == true {
			fmt.Printf("ORM\t%s\t%d\t%s\r\n", time.Now().Format("15:04:05"), 0, sqls)
		}
		row := z.Db.QueryRow(sqls)
		var ret bool
		row.Scan(&ret)
		return ret
	case "mysql":
		sqls := fmt.Sprintf("SELECT table_name FROM information_schema.TABLES WHERE table_name ='%s' AND TABLE_SCHEMA = '%s'", tabname, z.dbname)
		if z.isDebug == true {
			fmt.Printf("ORM\t%s\t%d\t%s\r\n", time.Now().Format("15:04:05"), 0, sqls)
		}
		rows, err := z.Db.Query(sqls)
		if err != nil {
			panic(err)
		}
		defer rows.Close()
		var dbs string
		for rows.Next() {
			if err := rows.Scan(&dbs); err != nil {
				panic(err)
			}
			if len(dbs) != 0 {
				return true
			}
			return false
		}
		return false
	}
	panic(z.dbtype)
	//return false
}

type TableInfo struct {
	Name   string              //命名空间
	DbName string              //数据库中名称
	GoName string              //go中的名称
	Fields map[string]*SFields //所有字段
	YZ     interface{}         //镜像
	Cols   []string            //列的名称，区分大小写
	//GoBaseName string              //go中的基本类型，比如[]*Suser 会写成Suser
	//Size   uintptr             //占用的空间大小
}

var fmTables map[string]*TableInfo //所有表内容 key是强制小写

//功能 对obj进行比对,如果库中没有,则添加到数据库中
//参数
//     models 模块列表，强制必须是指针
//注意 关键字强制为小写
func RegModels(namespace string, obj interface{}) {
	if fmTables == nil {
		fmTables = make(map[string]*TableInfo)
		//注册本系统需要用到的模块
		RegModels("suser", &SUser{})
		RegModels("saccess", &SAccess{}) //
		RegModels("srule", &SRule{})     //
		//		RegModels("sfields", &SFields{}) //
		RegModels("sgroup", &SGroup{}) //
	}

	if _, ok := fmTables[namespace]; ok == true {
		panic("命名空间已注册:" + namespace)
	}
	//tp := reflect.TypeOf(obj).Elem()
	ti := &TableInfo{YZ: obj}
	ti.Name = namespace
	ti.DbName = GetTableName(obj) //名称强制为结构名称
	ts := reflect.SliceOf(reflect.ValueOf(obj).Type()).String()[2:]
	ti.GoName = ts //设置GO中类型

	ti.Fields = make(map[string]*SFields)
	//ti.Size = tp.Size() //获取此结构长度

	cols, fields := getStructFields(obj)
	for i, v := range cols {
		ti.Cols = append(ti.Cols, v)
		ti.Fields[v] = fields[i]
	}
	fmTables[strings.ToLower(namespace)] = ti
}

//功能 获取类型的Go名称，用来做类型比较
func getGoName(obj interface{}) string {
	vl := reflect.ValueOf(obj)
	ret := vl.Type().String()
	return ret
}

//功能 获取类型的基本名称
func getGoBaseName(name string) string {
	sp := strings.Split(name, "*")
	name = sp[len(sp)-1]
	sp = strings.Split(name, "]")
	name = sp[len(sp)-1]
	return name
}

//功能 返回一个结构中的所有字段，及字段属性
func getStructFields(obj interface{}) ([]string, []*SFields) {
	var retcols []string
	var retsf []*SFields
	val := reflect.ValueOf(obj)
	//将val设置到最根部地址
	for {
		if val.Kind() != reflect.Ptr {
			break
		}
		val = val.Elem()
	}
	//将tp设置到最根部地址
	tp := reflect.TypeOf(obj)
	for {
		if tp.Kind() != reflect.Ptr {
			break
		}
		tp = tp.Elem()
	}
	for k := 0; k < val.NumField(); k++ {
		v := tp.Field(k)
		sf := &SFields{}
		sf.Name = v.Name
		tag := v.Tag.Get("zorm")
		if tag == "-" { //如果是-，则说明不添加到数据库中
			continue
		}
		retcols = append(retcols, v.Name) //列表是区分大小写的
		tags := strings.Split(tag, ";")
		for _, v := range tags {
			sp := strings.Split(v, ":")
			switch sp[0] {
			case "column":
				sf.Field = sp[1]
			case "type":
				switch sp[1] {
				case "int":
					sf.Type = TYPE_INT
				case "bool":
					sf.Type = TYPE_BOOL
				case "float64":
					sf.Type = TYPE_FLOAT64
				case "many2one":
					sf.Type = TYPE_MANY2ONE
				case "many2many":
					sf.Type = TYPE_MANY2MANY
				case "enum":
					sf.Type = TYPE_ENUM
				case "json", "jsonb":
					sf.Type = TYPE_JSON
				case "time.Time", "time", "timestamp":
					sf.Type = TYPE_DATETIME
				case "tablefield": //其它表的字段
					sf.Type = TYPE_TABLEFIELD
				default:
					panic("未识别的字段类型:" + sp[1])
				}
			case "index":
				if len(sp) == 1 { //如果没有设置索引名称，则使用名称索引
					sf.IndexName = sf.Field
				} else {
					sf.IndexName = sp[1]
				}
			case "default":
				sf.Default = sp[1]
			}
		}
		if len(sf.Field) == 0 { //如果数据库字段没有说明，则使用小写类型
			sf.Field = strings.ToLower(v.Name)
		}
		if sf.Type == 0 || (sf.Type == TYPE_MANY2ONE && len(sf.Relation) == 0) || (sf.Type == TYPE_MANY2MANY && len(sf.Relation) == 0) || (sf.Type == TYPE_JSON && len(sf.Relation) == 0) {
			ts := getGoName(val.Field(k).Interface())
			switch ts {
			case "uint8":
				sf.Type = TYPE_UINT8
			case "int16":
				sf.Type = TYPE_INT16
			case "uint16":
				sf.Type = TYPE_UINT16
			case "int32":
				sf.Type = TYPE_INT32
			case "int":
				sf.Type = TYPE_INT
			case "int64":
				sf.Type = TYPE_INT64
			case "bool":
				sf.Type = TYPE_BOOL
			case "string":
				sf.Type = TYPE_STRING
			case "float64", "float32":
				sf.Type = TYPE_FLOAT64
			case "time.Time":
				sf.Type = TYPE_DATETIME
			case "map[string]interface {}":
				sf.Type = TYPE_JSON
			default:
				if strings.Index(ts, "map[") == 0 {
					sf.Type = TYPE_JSON
					sf.Relation = ts
				} else if strings.Index(ts, "[]") != -1 {
					if sf.Type == 0 {
						sf.Type = TYPE_MANY2MANY
					}
					sf.Relation = ts
				} else if strings.Index(ts, ".") != -1 {
					if sf.Type == 0 {
						sf.Type = TYPE_MANY2ONE
					}
					sf.Relation = ts
				} else {
					panic(fmt.Sprintf("未处理的类型:%s", ts))
				}
			}
		}
		sf.TypeObj = val.Field(k).Interface() //保存类型
		if len(sf.Field) == 0 {               //如果字段为空，则出错
			panic(fmt.Sprintf("%v", obj))
		}
		if sf.Type == 0 { //类型不能为空
			panic(fmt.Sprintf("%v", obj))
		}
		sf.Offset = v.Offset
		retsf = append(retsf, sf)
	}
	return retcols, retsf
}

//功能 是否能够获取表名称
type CanTableName interface {
	TableName() string
}

//功能 获取指定类型的的表名
//参数
//     obj 类型，不能为字符串
func GetTableName(obj interface{}) string {
	if t, ok := obj.(CanTableName); ok == true {
		return t.TableName()
	} else if minfo, err := GetTableInfo(obj); err == nil {
		return minfo.DbName
	}
	n := reflect.TypeOf(obj)
	for {
		if n.Kind() != reflect.Ptr {
			break
		}
		n = n.Elem()
	}
	return GetSmailName(n.Name())
}

//功能 根据名称返回对应的实例
//注意 这里只能使用命名空间
func GetTableInterface(models interface{}) (interface{}, error) {
	if t, ok := models.(string); ok == true {
		obj, ok := fmTables[t]
		if ok == false {
			panic("故障,无法找到模块:" + t)
		}
		return reflect.New(reflect.ValueOf(obj.YZ).Elem().Type()).Interface(), nil //需要生成新的实例
	} else {
		return models, nil
	}
}

//功能，根据一个模块，找到对应的模块信息
func GetTableInfo(strptrormodel interface{}) (*TableInfo, error) {
	var tabname string
	if t, ok := strptrormodel.(string); ok == true {
		t = strings.Split(t, "*")[0]
		for _, v := range fmTables {
			if v.Name == t {
				return v, nil
			}
		}
		tabname = t
	} else {
		//比较类型
		t := getGoBaseName(getGoName(strptrormodel))
		for _, v := range fmTables {
			t1 := getGoBaseName(v.GoName)
			if t == t1 {
				return v, nil
			}
		}
		tabname = t
	}
	/*//出错信息
	fmt.Println("== 参考信息 =====================")
	for _, v := range fmTables {
		fmt.Printf("namespace:\t%-20s dbname:\t%s goname:\t%s\r\n", v.Name, v.DbName, v.GoName)
	}
	fmt.Println("================================")
	*/
	return nil, errors.New("未发现的模块:" + tabname)
}

//将一个名称，转换成相应的类名称
func GetSmailName(name string) string {
	sp := strings.Split(name, ".")
	ret := sp[len(sp)-1]
	sp = strings.Split(ret, "]")
	ret = sp[len(sp)-1]
	sp = strings.Split(ret, "*")
	ret = sp[len(sp)-1]
	return strings.ToLower(ret)
}

//功能 同步所有数据表
func (z *ZDB) SyncModel() {
	for _, v := range fmTables {
		z.CreateTable(v.YZ)
	}
}

//获取所有模块
func GetAllModels() map[string]interface{} {
	ret := make(map[string]interface{})
	for _, v := range fmTables {
		ret[v.Name] = v.YZ
	}
	return ret
}
